#pragma once

#include<iostream>
#include<string>
#include<functional>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include <netinet/in.h>//struct sockaddr_in
#include <arpa/inet.h>//inet_addr等



#include "Log.hpp"

using namespace LogModule;
							//这里是返回值    //这里是参数
using func_t = std::function<std::string(const std::string&)>;

const int defaultfd = -1;

//进行网络通信
class UdpServer
{
public:
	UdpServer(uint16_t port, func_t func)
		: _sockfd(defaultfd)
		, _port(port)
		, _isrunning(false)
		, _func(func)
		{}

		void Init()
		{							 //无连接的 UDP 协议
			_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
			if(_sockfd < 0)
			{
				LOG(LogLevel::FATAL) << "socket error!";
				exit(1);
			}
			LOG(LogLevel::INFO) << "socket success,sockfd: " << _sockfd;
			
			//2. 绑定socket信息，ip和端口
			// 2.1 填充sockaddr_in结构体
			struct sockaddr_in local;
			bzero(&local, sizeof(local));//内存初始化函数，主要用于将结构体或数组清零
			
			//用于指定地址族为 IPv4
			local.sin_family = AF_INET;//用于指定地址族 / AF_INET是个宏--2
			// 我会不会把我的IP地址和端口号发送给对方？
        	// IP信息和端口信息，一定要发送到网络！
        	// 本地格式->网络序列

								//主机字节序
			local.sin_port = htons(_port);// 设置端口号（网络字节序）
			//htons用于将 16 位整数从主机字节序转换为网络字节序

			// IP也是如此，1. IP转成4字节 2. 4字节转成网络序列 -> in_addr_t inet_addr(const char *cp);
        	//local.sin_addr.s_addr = inet_addr(_ip.c_str()); // TODO
			local.sin_addr.s_addr = INADDR_ANY;// 监听所有可用网络接口

			// 那么为什么服务器端要显式的bind呢？IP和端口必须是众所周知且不能轻易改变的！
			int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));
			if(n < 0)//绑定端口号
			{
				LOG(LogLevel::FATAL) << "bind error";
				exit(2);
			}
			LOG(LogLevel::INFO) << "bind success, sockfd: " << _sockfd;
		}


		//1. 客户端有数据需要处理
		//2. 客户端将数据发送给服务器
		//3. 服务器处理数据（通过_func）
		//4. 服务器将处理结果返回给客户端
		void Start()
		{
			_isrunning = true;
			while(_isrunning)
			{
				char buffer[1024];
				struct sockaddr_in peer;// 存储客户端地址信息
				socklen_t len = sizeof(peer);// 地址结构长度
				// 1. 收消息, client为什么要个服务器发送消息啊？不就是让服务端处理数据。
				//UDP 套接字接收数据
				ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);
				//@3减 1 是为了给字符串结束符\0预留位置   @4使用默认的接收标志  @5指向发送方地址结构的指针
				if(s > 0)
				{   //将网络字节序（大端序）转换为主机字节序
					int peer_port = ntohs(peer.sin_port);// 从网络中拿到的！网络序列
					//4字节网络风格的IP -> 点分十进制的字符串风格的IP
					std::string peer_ip = inet_ntoa(peer.sin_addr);

					buffer[s] = 0;// 添加字符串结束符

					//***核心：使之形成分层结构
					std::string result = _func(buffer);// 调用处理函数

					// 2. 发消息
					//sendto函数用于向客户端发送数据，把处理结果result发送回客户端
					sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr*)&peer, len);
					//(struct sockaddr*)&peer：这里存储着客户端的地址信息，包括 IP 地址和端口号
				}
			}
		}
		~UdpServer()
    	{
    	}
private:
	int _sockfd;//套接字描述符
	uint16_t _port;//unsigned short int
	bool _isrunning;

	func_t _func;// 服务器的回调函数，用来进行对数据进行处理
};
